﻿using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace thinger.ModbusLib
{
    public class ModbusRTU
    {
        private SerialPort serialPort = new SerialPort();

        /// <summary>
        /// 建立串口连接
        /// </summary>
        /// <param name="portName"></param>
        /// <param name="baudRate"></param>
        /// <param name="parity"></param>
        /// <param name="dataBits"></param>
        /// <param name="stopBits"></param>
        /// <returns></returns>
        public bool Connect(string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits)
        {
            serialPort.PortName = portName;
            serialPort.BaudRate = baudRate;
            serialPort.Parity = parity;
            serialPort.DataBits = dataBits;
            serialPort.StopBits = stopBits;
            try
            {
                serialPort.Open();
            }
            catch (Exception e)
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// 断开串口连接
        /// </summary>
        public void DisConnect()
        {
            if (serialPort.IsOpen)
            {
                serialPort.Close();
            }
        }

        // ushort(int16):占2字节
        /// <summary>
        /// 读取输出寄存器
        /// </summary>
        /// <param name="slaveId">从站</param>
        /// <param name="start">起始位置</param>
        /// <param name="count">读取多少个</param>
        /// <returns></returns>
        public byte[] ReadOutputRegisters(byte slaveId, ushort start, ushort count)
        {
            // 通信方法编写五步法:
            // 1.拼接报文

            // 从站地址+功能码+起始寄存器(2字节)+寄存器数量(2字节)+CRC(2字节)
            List<byte> SendCommand = new List<byte>();

            // 站地址 
            SendCommand.Add(slaveId);

            // 功能码(读取输出寄存器功能码:0x03)
            SendCommand.Add(0x03);

            // 起始寄存器
            // 怎么取2个字节的整数高位和低位,最简的方式是高位除以256,低位取模256
            SendCommand.Add((byte)(start / 256));
            SendCommand.Add((byte)(start % 256));

            // 如果是2个字节(A和B),要转换为整型的话,那么A*256+B=C
            //byte[] bytes = { 0x1A, 0x0A };
            // ushort占2字节
            //ushort result = (ushort)(bytes[0] * 256 + bytes[1]);
            //Console.WriteLine($"1A0A转为整数的结果为:{result}");

            // 寄存器数量
            SendCommand.Add((byte)(count / 256));
            SendCommand.Add((byte)(count % 256));

            // CRC校验
            byte[] crc = CalculateCRC(SendCommand.ToArray(), SendCommand.Count);

            // 将CRC校验结果,添加到报文尾部
            SendCommand.AddRange(crc);

            // 2.发送报文
            serialPort.Write(SendCommand.ToArray(), 0, SendCommand.Count);

            // 接收报文
            // 03功能码接收报文格式: 从站地址 + 功能码 + 字节计数 + 具体数据 + CRC
            // 串口的接收是需要有延时的(串口速度比较慢)
            Thread.Sleep(50);

            // 获取接收缓冲区有多少个字节
            int length = serialPort.BytesToRead;

            byte[] buffer = new byte[length];

            // 将接收的数据整个写到buffer中
            serialPort.Read(buffer, 0, buffer.Length);

            // 验证报文 (CRC验证)
            // 5 + count * 2   ==> 5代表固定长度(从站地址(1B)+功能码(1B)+字节计数(1B)+CRC(2B)),count*2:读取多少个*(2字节)得出接收报文的长度
            if (buffer.Length == 5 + count * 2 && CheckCRC(buffer) && buffer[0] == slaveId && buffer[1] == 0x03)
            {
                // 解析报文
                // count * 2:你读1个寄存器,要返回2个字节.读2个寄存器要返回4个字节
                byte[] result = new byte[count * 2];
                // 数组拷贝:buffer从索引3开始截取,3的位置是数据部分.copy到result中,result从0开始接收,总共拷贝count*2个字节
                Array.Copy(buffer, 3, result, 0, count * 2);

                return result;
            }
            else
            {
                return null;
            }
        }

        public bool WriteSingleRegister()
        {
            return true;
        }


        private static readonly byte[] aucCRCHi = {
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
             0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
             0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40
         };
        private static readonly byte[] aucCRCLo = {
             0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7,
             0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E,
             0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9,
             0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC,
             0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
             0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32,
             0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D,
             0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38,
             0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF,
             0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
             0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1,
             0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4,
             0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB,
             0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA,
             0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
             0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0,
             0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97,
             0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E,
             0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89,
             0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
             0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83,
             0x41, 0x81, 0x80, 0x40
         };

        /// <summary>
        /// CRC校验(ModbusRTU校验是:CRC16)
        /// </summary>
        /// <param name="pucFrame">字节数组</param>
        /// <param name="usLen">验证字节的长度</param>
        /// <returns>2个字节</returns>
        private static byte[] CalculateCRC(byte[] src, int usLen)
        {
            int i = 0;
            byte[] res = new byte[2] { 0xFF, 0xFF };
            ushort iIndex;
            while (usLen-- > 0)
            {
                iIndex = (ushort)(res[0] ^ src[i++]);
                res[0] = (byte)(res[1] ^ aucCRCHi[iIndex]);
                res[1] = aucCRCLo[iIndex];
            }
            return res;
        }

        private bool CheckCRC(byte[] value)
        {
            if (value == null) return false;
            if (value.Length <= 2) return false;

            // 这里的校验长度:value.Length-2,因为取到的数据要剔除掉校验位2个字节
            byte[] crc = CalculateCRC(value, value.Length - 2);

            // 比较最后2字节(最后2字节是CRC校验),判断是否一致
            if (crc[0] == value[value.Length - 2] && crc[1] == value[value.Length - 1])
            {
                return true;
            }

            return false;
        }

    }
}
